package com.tssup.fesco.common.utils.csv;


import com.google.common.collect.Lists;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.tssup.fesco.common.utils.DateUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;

import static java.lang.String.format;
import static java.util.stream.Collectors.toList;

/**
 * Created by neron.liu on 8/6/16.
 */
public class CsvBuilder<T> {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private static final String HEADER_KEY = "Content-Disposition";
    private static final String HEADER_VAL = "attachment; filename=\"%s\"";

    private HttpServletResponse response = null;

    private String fileName = "export.csv";

    private String fieldSeparator = ",";

    private List<T> data = null;

    private Class<T> dataType = null;

    public CsvBuilder(HttpServletResponse response, Class<T> dataType) {
        this.response = response;
        this.dataType = dataType;

        response.setContentType("text/csv; charset=gbk");
        response.setHeader(HEADER_KEY, format(HEADER_VAL, fileName));
    }

    public void write() throws IOException {
        List<Field> needToExports = getFields();

        writeTableHeader(needToExports);
        writeTableContent(needToExports);
    }

    public CsvBuilder setFileName(String fileName) {
        this.fileName = fileName;

        return this;
    }

    public CsvBuilder setData(List<T> data) {
        this.data = data;

        return this;
    }

    /**
     * 写表头
     *
     * @param needToExports
     * @throws IOException
     */
    private void writeTableHeader(List<Field> needToExports) throws IOException {
        StringBuilder header = new StringBuilder();

        List<Optional<String>> headers = needToExports.stream().map(this::getTableHeader).collect(toList());
        headers.forEach(e -> {
            if (e.isPresent()) {
                header.append(e.get());
                header.append(fieldSeparator);
            }
        });
        header.append("\n");

        writeToResponse(header);
    }

    /**
     * 写表内容
     *
     * @param needToExports
     */
    private void writeTableContent(List<Field> needToExports) {
        data.forEach(obj -> {
            StringBuilder line = new StringBuilder();

            buildLine(needToExports, obj, line);

            try {
                writeToResponse(line);
            } catch (IOException e) {
                logger.error("Export error!", e);
            }
        });
    }

    private void buildLine(List<Field> needToExports, T obj, StringBuilder line) {
        for (Field field : needToExports) {
            try {
                Object value = getValue(field, obj);
                if (value == null){
                    value = "";
                }

                if (value instanceof Integer) {
                    Pair<Boolean, String[]> result = isStatusField(field);
                    if (isStatusField(field).getLeft()) {
                        String[] definitions = result.getRight();
                        for (String def : definitions) {
                            String[] statusAndName = def.split("_");

                            String status = String.valueOf(value);
                            if (statusAndName[0].toLowerCase().startsWith(status.toLowerCase()) && statusAndName.length >= 2) {
                                line.append(statusAndName[1]);
                                break;
                            }
                        }
                    } else {
                        line.append(value);
                    }
                } else if (value instanceof Boolean) {
                    line.append(Boolean.TRUE.equals(value)?"是":"否" );
                } else if (value instanceof Date) {
                    line.append(DateUtils.convertDate2String(DateUtils.DEFAILT_DATE_TIME_PATTERN, (Date)value));
                } else if (value instanceof String){
                    value = ((String) value).replace(',', ' ');
                    line.append(value);
                } else {
                    line.append(value);
                }
                line.append(fieldSeparator);
            } catch (IllegalAccessException e) {
                logger.error("Export error!", e);
            }
        }
        line.append("\n");
    }

    private Object getValue(Field field, Object object) throws IllegalArgumentException, IllegalAccessException {
        Object result = null;
        String fieldName = field.getName();
        StringBuffer getMethodName = new StringBuffer();
        String firstChar = fieldName.substring(0, 1);
        getMethodName.append("get").append(firstChar.toUpperCase()).append(fieldName.substring(1));
        try {
            Method getMethod = object.getClass().getMethod(getMethodName.toString());
            result = getMethod.invoke(object);
        } catch(Exception e) {
            result = field.get(object);
        }
        return result;
    }

    /**
     * 获取字段,并修改访问性
     *
     * @return
     */
    private List<Field> getFields() {
        List<Field> fields = Lists.newArrayList();
        Class<?> clazz = dataType;
        while(clazz != null && clazz != Object.class) {
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            clazz = clazz.getSuperclass();
        }
        return fields.stream().filter(this::isRequiredField).map(f -> {
            f.setAccessible(true);
            return f;
        }).collect(toList());
//        return null;
//        Field[] fields = dataType.getFields();
//        return Stream.of(fields)
//                .filter(this::isRequiredField)
//                .map(f -> {
//                    f.setAccessible(true);
//                    return f;
//                })
//                .collect(toList());
    }

    /**
     * 获取通过定义了CsvHeader的表头
     *
     * @param field
     * @return
     */
    private Optional<String> getTableHeader(Field field) {
        for (Annotation annotation : field.getDeclaredAnnotations()) {
            if (annotation instanceof CsvHeader) {
                return Optional.of(((CsvHeader) annotation).value());
            }
        }

        return Optional.empty();
    }

    private boolean isRequiredField(Field field) {
        for (Annotation annotation : field.getDeclaredAnnotations()) {
            if (annotation instanceof CsvIgnore) {
                return false;
            }
        }

        return true;
    }

    private Pair<Boolean, String[]> isStatusField(Field field) {
        for (Annotation annotation : field.getDeclaredAnnotations()) {
            if (annotation instanceof CsvStatusValue) {
                return Pair.of(Boolean.TRUE, ((CsvStatusValue) annotation).value());
            }
        }

        return Pair.of(Boolean.FALSE, null);
    }

    private void writeToResponse(StringBuilder line) throws IOException {
        response.getWriter().write(line.toString());
        response.getWriter().flush();
    }

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CsvIgnore {

    }

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CsvHeader {
        String value();
    }

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CsvStatusValue {
        String[] value();
    }

}